在前面的章節已經介紹了 Next.js 是 file-based routing 的架構,在路由至其他頁面時,通常會使用到 <Link /> 這個 component,這個 component 提供不少的 props 可以針對不同的情況做設定,今天我們要了解 prefetch 這個 props 的功用,它可以讓切換頁面更有效率。
要注意的是
prefetch只在 production 環境有用!
也許你會需要修改 global.css: https://gist.github.com/leochiu-a/5276029a65017c660bb91dcba6bab53c
一個很常見的起手式,我們使用 create-next-app 以 typescript 啟動一個 Next.js 的專案:
$ npx create-next-app --typescript
創建完後,你可以在 pages/ 這個資料夾中看到類似以下的資料夾結構,原本預設的檔案沒有 products.tsx ,這是後來加上去的,我們將使用 / 與 /products 兩個頁面來做實驗:

我們將 pages/index.tsx 的內容改成以下這個模樣,首頁中的內容只有包含一個 <Link> ,可以點擊路由到 /products 頁面。在前面的章節介紹過 <Link> 裡面的 children 只能是 string 或是 HTML 的 <a> ,如果使用其他的 tag 則會報錯:
import type { NextPage } from "next";
import Link from "next/link";
const Home: NextPage = () => {
return (
<Link href="/products">
<a>link to products</a>
</Link>
);
};
export default Home;
接著看到 pages/products.tsx 的內容,它是一個 SSR 的頁面,透過 getServerSideProps 傳遞一個 products 的字串,並在 component 中渲染這個字串,非常地單純:
interface Props {
products: string;
}
const Products = ({ products }: Props) => {
return <div>{products}</div>;
};
export const getServerSideProps = async () => {
return {
props: { products: "products" },
};
};
export default Products;
各位讀者要先知道「 prefetch 只在 production 環境有用」,亦即使用 yarn dev 啟動開發用伺服器什麼事情都不會發生,我們要使用 yarn build 與 yarn start 將 Next.js 應用打包後,再啟動伺服器:
$ yarn build
$ yarn start
接著你可以在瀏覽器中看到 [http://localhost:3000](http://localhost:3000) 中的首頁內容如下:

使用 Chrome 的 devtool 看看 /prodcuts 的 chunk 是不是已經被預先載入了:
cmd + option + i)你可以看到 /products 的 chunk 已經被預先下載,而且 Size 的欄位是 prefetch cache,意思是會儲存在 Chrome 的 prefetch cache 中:

/products 的 chunk 可以預先載入?Next.js 之所以可以做到這件事要歸功於 HTML 的 <link> ,在 <head> 加上以下這種寫法,可以「預先載入未來可能被用到的資源」:
<link rel="prefetch" href="/images/big.jpeg" />
在 pages/index.tsx 這個頁面中可以看到加入了 <Link href='/products'> 的內容,而 Next.js 知道使用者可能會點擊連結切換到 /products 這個頁面,所以便讓這個頁面成為會被「prefetch」的資源。

/products 頁面被 prefetch在 Next.js 9 以後,使用 <Link> 都會被預設加入到 prefetch 的資源中,如同上面看到的例子,雖然我們沒有在 <Link> 傳入 prefetch={true} ,但是 /products 這個頁面仍然會被加入到 prefetch 的資源中。
但是有時候並不是所有使用 <Link> 的頁面都需要被 prefetch,所以很直覺地在 <Link> 上傳入 prefetch={false} ,將會使得該頁面不會被 prefetch。
我們以 pages/index.tsx 這個頁面中的 <Link> 為例,修改頁面中的內容:
<Link href="/products" prefetch={false}>
<a>link to products</a>
</Link>
同樣執行 yarn build 與 yarn start 後,瀏覽該頁面的 Elements tab,會發現在 <head> 中找不到 /products 被預先載入的訊息。

當然,如果再切換到 Network tab,就看不到 /products 的 chunk 被載入。
<Link> 會觸發 prefetch 嗎?在頁面上總是會有一些 <Link> 並非是一開始就渲染在頁面上,而是使用者跟頁面互動過後,達成某些條件才渲染在畫面上,讀者們也許就會有這樣的問題「條件式渲染的 <Link> 也可以讓頁面的 chunk 被 prefetch 嗎?」
我們來實驗看看,修改 pages/index.tsx 中的內容,以條件式渲染的方式顯示 <Link> ,在使用者點擊按鈕後, <Link> 才會出現在畫面上:
const Home: NextPage = () => {
const [visible, setVisible] = useState(false);
return (
<div>
{visible && (
<Link href="/products">
<a>link to products</a>
</Link>
)}
<button onClick={() => setVisible(true)}>show link</button>
</div>
);
};
同樣執行 yarn build 與 yarn start 後,瀏覽該頁面的 Elements tab,會發現在 <head> 中找不到 /products 被預先載入的訊息。
但是,在點擊「show link」的按鈕之後,你會發現 <head> 中動態載入了以下內容,讓 /prodcuts 的 chunk 成為會被預先載入的資源:

此時再打開 Network tab,原本沒有預先載入的 products chunk 資源,也在 <link> 被設定後被載入儲存到 prefetch cache 中。

router.push 的自定義路由也能夠 prefetch 嗎?在有些情況 <Link> 也許不能夠滿足需求,必須使用 next/router 的 useRouter 在切換頁面之前執行一些操作,例如驗證表單、 GA 事件等等,在這種情況要怎麼 prefetch 頁面呢?
可以使用 router.prefetch 將指定的頁面作為 prefetch 的頁面,這種做法跟 conditional rendering 的狀況有些相似,從「檢視原始碼」中會發現原本的頁面中是不包含 <link rel="prefetch"> 的資源,但是在頁面載入之後, router.prefetch 再動態地指定 prefetch 的資源,讓瀏覽器自動預先抓取資源。
const router = useRouter();
useEffect(() => {
router.prefetch("/products");
}, [router]);
要特別注意的是,
router.prefetch在yarn dev下不會起作用。
以下為 pages/index.tsx 的範例程式,在頁面載入時動態地 prefetch /products 的頁面 chunk,在點擊按鈕後觸發 handleClick() 後,此時 /products 頁面已經被預先載入,所以就可以有效縮減 /products 的頁面載入時間
const Home: NextPage = () => {
const router = useRouter();
useEffect(() => {
router.prefetch("/products");
}, [router]);
const handleClick = () => {
router.push("/products");
};
return (
<div>
<button onClick={handleClick}>to /products</button>
</div>
);
};
在這篇文章中,我們了解了 Next.js 的 prefetch 機制,並且知道 <Link> 在預設的情況下都會 prefetch 頁面。如果是在 conditional rendering 的頁面中,只要 <Link> 被渲染後,也可以動態地 prefetch 指定的頁面。
而有時候 <Link> 無法滿足我們的需求,會使用到 router.push 這種自定義路由的方式,此時可以使用 router.prefetch 達到 prefetch 頁面的效果。
最後,再次提醒不論是 <Link> 或是 router.prefetch,prefetch 這個特性只有在 production 的環境下才能使用, yarn dev 是無法觸發 prefetch 資源的 。